# Python CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-python/ for more details
#
# Adopted from
# https://github.com/facebookresearch/detectron2/blob/master/.circleci/config.yml

version: 2.1

parameters:
  run-tests:
    type: boolean
    default: false

# -------------------------------------------------------------------------------------
# Environments to run the jobs in
# -------------------------------------------------------------------------------------
cpu_py38: &cpu_py38
  docker:
    - image: cimg/python:3.8
  resource_class: large
  environment:
    # We're a bit short on RAM
    MAX_JOBS: "4"

gpu_cu118: &gpu_cu118
  environment:
    CUDA_VERSION: "11.4"
    CUDA_HOME: /usr/local/cuda-11.4
    XFORMERS_FORCE_DISABLE_TRITON: "1"
  machine:
    # See https://circleci.com/docs/linux-cuda-images-support-policy/
    image: linux-cuda-11:default
    resource_class: gpu.nvidia.medium
  working_directory: ~/xformers


binary_common: &binary_common
  parameters:
    pytorch_version:
      description: "PyTorch version to build against"
      type: string
      default: "1.10.0"
    python_version:
      description: "Python version to build against (e.g., 3.7)"
      type: string
      default: "3.8"
    cu_version:
      description: "CUDA version to build against, in CU format (e.g., cpu or cu100)"
      type: string
      default: "cu102"
    wheel_docker_image:
      description: "Wheel only: what docker image to use"
      type: string
      default: "pytorch/manylinux-cuda102"
  environment:
      CU_VERSION: << parameters.cu_version >>
      PYTHON_VERSION: << parameters.python_version >>
      PYTORCH_VERSION: << parameters.pytorch_version >>
      XFORMERS_VERSION_SUFFIX: ""

# -------------------------------------------------------------------------------------
# Re-usable commands
# -------------------------------------------------------------------------------------
setup_conda: &setup_conda
  - run:
      name: Setup Conda
      working_directory: ~/
      command: |
        cd /home/circleci
        echo 'export MINICONDA=$HOME/miniconda' >>  $BASH_ENV
        echo 'export PATH="$MINICONDA/bin:$PATH"' >>  $BASH_ENV
        echo 'export CONDA_PYTHON=/home/circleci/venv/bin/python'  >>  $BASH_ENV
        source $BASH_ENV

        # check if we have restored venv cache (/home/circleci/venv) correctly, if so, just skip
        if [ -f /home/circleci/venv/check_version.py ]; then $CONDA_PYTHON /home/circleci/venv/check_version.py torch gt 1.11 && exit 0; fi

        hash -r
        wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh
        bash miniconda.sh -b -f -p $MINICONDA
        conda config --set always_yes yes
        conda update conda
        conda info -a
        conda create -p /home/circleci/venv python=3.8.0 pip  # pip is required here, else the system pip will be used


install_dep: &install_dep
  - run:
      name: Install Dependencies with torch nightly
      no_output_timeout: 30m
      command: |
        source $BASH_ENV

        # check if we have restored venv cache (/home/circleci/venv) correctly, if so, just skip
        if [ -f /home/circleci/venv/check_version.py ]; then $CONDA_PYTHON /home/circleci/venv/check_version.py torch gt 2.1 && exit 0; fi

        # start installing
        source activate /home/circleci/venv

        # for faster builds
        conda install ninja
        echo "Ninja version $(ninja --version)"

        conda install pytorch torchvision torchaudio pytorch-cuda=11.8 -c pytorch -c nvidia -q

        $CONDA_PYTHON -m pip install cmake
        $CONDA_PYTHON -m pip install -r requirements-benchmark.txt --progress-bar off

        # Mark install as complete
        touch /home/circleci/miniconda/.finished

install_repo: &install_repo
  - run:
      name: Install Repository
      no_output_timeout: 30m
      command: |
        source $BASH_ENV
        source activate /home/circleci/venv
        git submodule update --init --recursive
        $CONDA_PYTHON -m pip install -v -e .

        # Test import.
        $CONDA_PYTHON -c 'import sys; sys.path = sys.path[1:]; import xformers'
        ls xformers
        $CONDA_PYTHON -m xformers.info


run_coverage: &run_coverage
  - run:
      name: Run Unit Tests With Coverage
      when: always
      command: |
        source $BASH_ENV
        $CONDA_PYTHON -m pytest --junitxml=test-results/junit.xml --verbose --maxfail=20 --cov-report=xml --cov=./ tests
        #Uploading test coverage for Python code
        bash <(curl -s https://codecov.io/bash) -f coverage.xml -cF Python

run_unittests: &run_unittests
  - run:
      name: Run Unit Tests
      when: always
      command: |
        source $BASH_ENV
        $CONDA_PYTHON -m pytest --junitxml=test-results/junit.xml --verbose --maxfail=20 tests

run_doc_build: &run_doc_build
   - run:
      name: Testing doc build
      when: always
      command: |
        source $BASH_ENV
        source activate /home/circleci/venv
        cd docs
        # Don't install PyTorch as we already pulled it from conda, and we'd risk having two conflicting versions
        $CONDA_PYTHON -m pip install $(grep -ivE "^#|^torch" requirements.txt)
        make help
        make singlehtml | tee make.out
        ! tail make.out | grep -q warning

commands:
    setup_pyenv:
      parameters:
        version:
          type: string
      steps:
        - run:
            name: Setup pyenv
            command: |
              git clone -b master https://github.com/pyenv/pyenv-update.git $(pyenv root)/plugins/pyenv-update
              cd $(pyenv root); git checkout master; cd /home/circleci
              pyenv update
              # figure out the latest python3version given a subversion, like 3.8
              LATEST_PY_VERSION=$(pyenv install --list | sed 's/^  //' | grep -E '^[0-9].[0-9].[0-9]' | grep <<parameters.version>> | tail -1)
              pyenv install -f $LATEST_PY_VERSION
              pyenv global $LATEST_PY_VERSION

    check_torch: &check_torch
      parameters:
        major:
          type: integer
        minor:
          type: integer

      steps:
        - run:
            name: Check the installed PyTorch version
            command: |
              source $BASH_ENV
              which python

              $CONDA_PYTHON -c 'import torch; print("Torch version:", torch.__version__)'
              $CONDA_PYTHON -c 'import torch; assert torch.__version__ > ( <<parameters.major>>,  <<parameters.minor>>), "wrong torch version"'
              $CONDA_PYTHON -m torch.utils.collect_env
              wget -O ~/venv/check_version.py https://raw.githubusercontent.com/min-xu-ai/check_verion/main/check_version.py

    run_gpu_ci: &run_gpu_ci
      parameters:
        arch:
          type: string
      steps:
        - checkout

        - run: nvidia-smi
        - run:
            name: Setup env variables
            command: |
              echo 'export TORCH_CUDA_ARCH_LIST="<<parameters.arch>>"' >>  $BASH_ENV
              echo 'export FORCE_CUDA=1' >>  $BASH_ENV

        # Cache the venv directory that contains dependencies
        - restore_cache:
            keys:
              - cache-key-gpu-arch<<parameters.arch>>-{{ checksum "requirements-test.txt" }}-{{ checksum "requirements-benchmark.txt" }}-{{ checksum ".circleci/continue_config.yml" }}

        - <<: *setup_conda
        - <<: *install_dep

        - check_torch:
            major: 2
            minor: 1

        - save_cache:
            paths:
              - ~/miniconda
              - ~/venv

            key: cache-key-gpu-arch<<parameters.arch>>-{{ checksum "requirements-test.txt"}}-{{ checksum "requirements-benchmark.txt" }}-{{ checksum ".circleci/continue_config.yml"}}

        - <<: *install_repo
        - <<: *run_coverage

        - store_test_results:
            path: test-results

# -------------------------------------------------------------------------------------
# Jobs to run
# -------------------------------------------------------------------------------------

jobs:
  skip_circleci_tests:
    machine:
      image: ubuntu-2004:2023.07.1
    steps:
      - run: echo "No job to run"
  cpu_tests_py38:
    <<: *cpu_py38

    working_directory: ~/xformers

    steps:
      - checkout

      - run:
          name: Remove internal components, as they require GPU
          command: |
            mkdir -p .github/sync.fairinternal
            touch .github/sync.fairinternal/ossify.sh
            chmod +x .github/sync.fairinternal/ossify.sh
            .github/sync.fairinternal/ossify.sh

      # Cache the venv directory that contains dependencies
      - restore_cache:
          keys:
            - cache-key-cpu-py38-{{ checksum "requirements-test.txt" }}-{{ checksum "requirements-benchmark.txt" }}-{{ checksum ".circleci/continue_config.yml" }}

      - <<: *setup_conda

      - <<: *install_dep

      - check_torch:
          major: 2
          minor: 1

      - save_cache:
          paths:
            - ~/miniconda
            - ~/venv

          key: cache-key-cpu-py38-{{ checksum "requirements-test.txt" }}-{{ checksum "requirements-benchmark.txt" }}-{{ checksum ".circleci/continue_config.yml" }}

      - <<: *install_repo
      - <<: *run_unittests
      - <<: *run_doc_build

      - store_test_results:
          path: test-results

  gpu_tests_cu118_sm75_T4:
    <<: *gpu_cu118
    machine:
      image: linux-cuda-11:default
      resource_class: gpu.nvidia.medium  # T4

    steps:
      - run_gpu_ci:
          arch: "7.5"

workflows:
  version: 2
  build:
    when: << pipeline.parameters.run-tests >>
    jobs:
      - cpu_tests_py38
      - gpu_tests_cu118_sm75_T4
  # Prevents marking the CI as failed when no jobs run
  # https://github.com/CircleCI-Public/circleci-cli/issues/577
  report_noop:
    when:
      not: << pipeline.parameters.run-tests >>
    jobs:
      - skip_circleci_tests
